package com.hyzx.qbasic.admin.common.config;

import com.hyzx.qbasic.admin.common.security.ShiroUtils;
import com.hyzx.qbasic.admin.common.util.*;
import com.hyzx.qbasic.admin.model.annotation.DbLogIgnore;
import com.hyzx.qbasic.admin.model.dto.AdminUserInfo;
import com.hyzx.qbasic.admin.model.po.BusinessLogPO;
import com.hyzx.qbasic.admin.domain.service.ILogService;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * Created by 黄江华 on 2017/9/29.
 * 通用日志切面，实现：1、通用业务日志记录 2、性能 3、异常
 */

@SuppressWarnings("Duplicates")
@Aspect
@Component
@DependsOn("logService")
public class RequestFilter {
    private static final Logger logger = LoggerFactory.getLogger(RequestFilter.class);

    @Autowired
    private ILogService logService;

    @Pointcut("within(com.hyzx.qbasic.admin.controller..*)")
    public void executeService() {
    }

    @Around("executeService()")
    public Object aroundFacadeExecution(ProceedingJoinPoint joinPoint) {
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method method = ms.getMethod();
        String methodName = ms.toShortString();

        boolean isRestfulApi = RequestFilterUtils.isRestfulApi(method);
        String logInfo = RequestFilterUtils.getMethodInfo(method, methodName);

        String userName = ShiroUtils.getCurrentUserName();
        if (StringUtils.isEmpty(userName))
            userName = StringUtils.EMPTY;

        Object res = null;
        long duration = 0L;
        String reqParams = StringUtils.EMPTY;
        Object[] params = joinPoint.getArgs();

        //请求参数序列化成JSON字符串
        try {
            reqParams = RequestFilterUtils.getRequestParams(params);
        } catch (Exception ex) {
            logger.error(String.format("方法：%s ,JSON序列化异常，请检查请求入参", logInfo), ex);
        }

        //校验请求参数合法性
        try {
            for (Object param : params) {
                boolean needVerify = param instanceof BindingResult;
                if (!needVerify)
                    continue;

                BindingResult result = (BindingResult) param;
                if (result.hasErrors()) {
                    FieldError fieldError = result.getFieldError();

                    logger.info("{}访问了:{}，请求参数：{},参数验证错误：{}", userName, logInfo, reqParams,
                            fieldError.getDefaultMessage());

                    return onParamError(isRestfulApi, fieldError.getDefaultMessage());
                }
            }
        } catch (Exception ex) {
            logger.error(String.format("校验请求参数方法：%s异常，请求参数：%s", logInfo, reqParams), ex);
        }

        try {
            long startTime = System.currentTimeMillis();

            res = joinPoint.proceed(params);

            duration = System.currentTimeMillis() - startTime;

        } catch (Exception ex) {
            res = onRequestException(isRestfulApi);
            logger.error(String.format("方法：%s异常，请求参数：%s", logInfo, reqParams), ex);
        } catch (Throwable throwable) {
            res = onRequestException(isRestfulApi);
            logger.error(String.format("方法：%s异常，请求参数：%s", logInfo, reqParams), throwable);
        }

        logger.info("{}访问了:{}，请求参数：{},耗时：{}ms", userName, logInfo, reqParams, duration);

        //业务日志异步写数据库
        DbLogIgnore dbLogIgnore = method.getAnnotation(DbLogIgnore.class);
        if (dbLogIgnore == null)
            writeBusinessLog(methodName, logInfo, duration, reqParams);

        return res;
    }

    //业务日志写数据库
    private void writeBusinessLog(String methodName, String logInfo, long duration, String reqParams) {
        try {
            HttpServletRequest request = HttpContextUtils.getHttpServletRequest();

            AdminUserInfo currentUser = ShiroUtils.getCurrentUserInfo();
            String userName = currentUser != null ? currentUser.getUserName() : null;
            Integer userId = currentUser != null ? currentUser.getUserId() : null;

            BusinessLogPO businessLog = new BusinessLogPO();
            businessLog.setUserId(userId);
            businessLog.setUserName(userName);
            businessLog.setOperation(logInfo);
            businessLog.setTime(duration);
            businessLog.setMethod(methodName);
            businessLog.setParams(reqParams);
            businessLog.setIp(IpUtils.getIpAddress(request));
            businessLog.setCreateTime(new Date());

            ThreadPoolUtils.execute(() -> {
                logService.addBusinessLog(businessLog);
            });
        } catch (Exception ex) {
            logger.error("业务日志写数据库异常", ex);
        }
    }

    private Object onRequestException(boolean isRestfulApi) {
        if (isRestfulApi)
            return BaseResUtils.serverError();
        else
            return "/error/500";
    }

    private Object onParamError(boolean isRestfulApi, String error) {
        if (isRestfulApi)
            return BaseResUtils.paramError(error);
        else
            return "/error/400";
    }
}
